// Copyright (c) 2009, Michael Patraw
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//     * Redistributions of source code must retain the above copyright
//       notice, this list of conditions and the following disclaimer.
//     * Redistributions in binary form must reproduce the above copyright
//       notice, this list of conditions and the following disclaimer in the
//       documentation and/or other materials provided with the distribution.
//     * The name of Michael Patraw may not be used to endorse or promote 
//       products derived from this software without specific prior written
//       permission.
//
// THIS SOFTWARE IS PROVIDED BY Michael Patraw ''AS IS'' AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL Michael Patraw BE LIABLE FOR ANY
// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include <sstream>

#include "game.h"

Game::Game(World * w) :
world_(w), maprect_(0, 0, PLAY_WINDOW_W, PLAY_WINDOW_H)
{
}

Game::~Game()
{
    if (world_)
        delete world_;
}

void Game::run()
{
    Command cmd;
    std::string deathmessage = "You have died from ";
    bool update = true; // Update the world.

    world_->update(); // Start some of the cold turkey systems.
    while (at::is_running()) {
        draw();

        UpdateTui();
        ClearTui();
        
        cmd = GetCommand(at::getkey());
        
        switch (cmd) {
            case CMD_QUIT:
                at::stop_running();
                break;
            default:
                if (!world_->process(cmd))
                    update = false;
                break;
        }
        
        if (update)
            world_->update();
        update = true;
        
        // Check for player death.
        if (world_->player()->hasCondition(COND_DEAD)) {
            MessWindow()->addstr(0, 0, (deathmessage + world_->player()->getDeathReason()).c_str(), Red);
            MessWindow()->addstr(0, 1, "Press 'Q' to continue.", Yellow);
            draw();
            UpdateTui();
            ClearTui();
            
            while (at::getkey() != 'Q')
                ;
            
            drawDeath();
            at::stop_running();
        }
    }
}

void Game::draw()
{
    centerMap();
    if (world_->getTime()->isDay())
        drawLightMap();
    else if (world_->getTime()->isNight())
        drawDarkMap();
    else
        drawMap();
    drawPlayer();
    drawInfo();
    drawMessages();
}

void Game::centerMap()
{
    // Set x and y to the player's location minus half the screen width.
    maprect_.setX(world_->player()->getCoord().getX() - (PLAY_WINDOW_W >> 1));
    maprect_.setY(world_->player()->getCoord().getY() - (PLAY_WINDOW_H >> 1));
}

void Game::drawMap() const
{    
    // Map coordinates.
    int mx, my;
    
    // Loop through the window, then convert it to map locations based on maprect_
    for (int x = 0; x < PLAY_WINDOW_W; ++x) {
        for (int y = 0; y < PLAY_WINDOW_H; ++y) {
            mx = x + maprect_.getX();
            my = y + maprect_.getY();
            // Out of bounds check.
            if (mx >= 0 && mx < world_->map()->getWidth() && my >= 0 && my < world_->map()->getHeight()) {
                // Draw at offset.
                PlayWindow()->addch(x, y, world_->map()->getTile(mx, my)->getGlyph().getAscii(),
                    world_->map()->getTile(mx, my)->getGlyph().getColor());
            }
            else {
                PlayWindow()->addch(x, y, 1, at::color(0x20, 0x20, 0x20));
            }
        }
    }
}

void Game::drawDarkMap() const
{
    // Map coordinates.
    int mx, my;
    
    // Loop through the window, then convert it to map locations based on maprect_
    for (int x = 0; x < PLAY_WINDOW_W; ++x) {
        for (int y = 0; y < PLAY_WINDOW_H; ++y) {
            mx = x + maprect_.getX();
            my = y + maprect_.getY();
            // Out of bounds check.
            if (mx >= 0 && mx < world_->map()->getWidth() && my >= 0 && my < world_->map()->getHeight()) {
                // Draw at offset.
                PlayWindow()->addch(x, y, world_->map()->getTile(mx, my)->getGlyph().getAscii(),
                    world_->map()->getTile(mx, my)->getGlyph().getDarkColor());
            }
            else {
                PlayWindow()->addch(x, y, 1, at::color(0x20, 0x20, 0x20));
            }
        }
    }
}

void Game::drawLightMap() const
{
    // Map coordinates.
    int mx, my;
    
    // Loop through the window, then convert it to map locations based on maprect_
    for (int x = 0; x < PLAY_WINDOW_W; ++x) {
        for (int y = 0; y < PLAY_WINDOW_H; ++y) {
            mx = x + maprect_.getX();
            my = y + maprect_.getY();
            // Out of bounds check.
            if (mx >= 0 && mx < world_->map()->getWidth() && my >= 0 && my < world_->map()->getHeight()) {
                // Draw at offset.
                PlayWindow()->addch(x, y, world_->map()->getTile(mx, my)->getGlyph().getAscii(),
                    world_->map()->getTile(mx, my)->getGlyph().getLightColor());
            }
            else {
                PlayWindow()->addch(x, y, 1, at::color(0x20, 0x20, 0x20));
            }
        }
    }
}

void Game::drawPlayer() const
{
    // Always draws on the center of the screen.
    PlayWindow()->addch(PLAY_WINDOW_W >> 1, PLAY_WINDOW_H >> 1,
        world_->player()->getGlyph().getAscii(),
        world_->player()->getGlyph().getColor());
}

void Game::drawInfo()
{
    std::stringstream ss;
    // Draws all the player and world information.
    
    // Name
    InfoWindow()->addstr(1, 0, world_->player()->getName(), White);
    
    // Season
    switch (world_->getTime()->getSeason()) {
        case SPRING:
            InfoWindow()->addstr(1, 2, "Spring", White);
            break;
        case SUMMER:
            InfoWindow()->addstr(1, 2, "Summer", White);
            break;
        case FALL:
            InfoWindow()->addstr(1, 2, "Fall", White);
            break;
        case WINTER:
            InfoWindow()->addstr(1, 2, "Winter", White);
            break;
        default:
            break;
    }
    // Time
    InfoWindow()->addstr(1, 3, world_->getTime()->getTime(), White);
    
    // Weather
    InfoWindow()->addstr(1, 5, world_->getWeather()->getWeather(), White);
    
    // Stats
    ss << "Health: "
       << world_->player()->health()->current();
    InfoWindow()->addstr(1, 8, ss.rdbuf()->str().c_str(), White); // Health.
    ss.str("");
    ss.clear();
    
    // Conditions, spans across more lines with more conditions.
    ss << world_->player()->getConditionNames();
    // Insert newlines for long condtion names.
    std::size_t loc = ss.rdbuf()->str().length() - 1;
    while (loc >= (unsigned)INFO_WINDOW_W && loc != std::string::npos) {
        loc = ss.rdbuf()->str().rfind(",");
    }
    if (loc != std::string::npos)
        ss.rdbuf()->str().insert(loc, "\n");
    InfoWindow()->addstr(1, 11, ss.rdbuf()->str().c_str(), White);
    ss.str("");
    ss.clear();
    
    // Location info. Bottom of message window.
    ss << "some "
       << world_->map()->getTile(
        world_->player()->getCoord().getX(),
        world_->player()->getCoord().getY()
    )->getName();
    InfoWindow()->addstr(1, INFO_WINDOW_H-2, "Standing around:", White);
    InfoWindow()->addstr(1, INFO_WINDOW_H-1, ss.rdbuf()->str().c_str(), White);
}

void Game::drawMessages()
{
    std::stringstream ss;
    Coord pc = world_->player()->coord();
    ItemList * list = world_->map()->tile(pc.getX(), pc.getY())->getItems();
    
    if (list->numItems() == 1) {
        ss << "You see "
           << list->getItem(0)->getName()
           << " here.";
        MessWindow()->addstr(0, MESS_WINDOW_H-1, ss.rdbuf()->str().c_str(), White);
    }
    else if (list->numItems() > 1) {
        MessWindow()->addstr(0, MESS_WINDOW_H-1, "You see some stuff here.", White);
    }
    else {
        MessWindow()->addstr(0, MESS_WINDOW_H-1, "You see no stuff here.", White);
    }
}

void Game::drawDeath()
{
    PlayWindow()->addstr(0, 0, "You persisted as long as possible, but in the end ", White);
    PlayWindow()->addstr(0, 1, "all things must come to end, and you're no", White);
    PlayWindow()->addstr(0, 2, "different. Sad yours came so soon, though.", White);
    PlayWindow()->addstr(0, 4, "Press and key to quit.", Yellow);
    
    UpdateTui();
    ClearTui();
    
    at::getkey();
}
